Skip to content

WebGPURenderer: Surface uncaptured GPU errors and WGSL diagnostics#33418

Open
RenaudRohlinger wants to merge 4 commits intomrdoob:devfrom
RenaudRohlinger:renaud/webgpu-error-reporting
Open

WebGPURenderer: Surface uncaptured GPU errors and WGSL diagnostics#33418
RenaudRohlinger wants to merge 4 commits intomrdoob:devfrom
RenaudRohlinger:renaud/webgpu-error-reporting

Conversation

@RenaudRohlinger
Copy link
Copy Markdown
Collaborator

@RenaudRohlinger RenaudRohlinger commented Apr 19, 2026

When a WebGPU render or compute pipeline fails today, three.js logs an opaque [Invalid ShaderModule "fragment"] is invalid, no line number, no hint at the real WGSL mistake. Downstream uncaptured errors (bad buffer writes, invalid command-buffer submits) are not surfaced through the renderer at all; they only appear as raw WebGPU entries in the browser console.
This PR uses more of the spec to fix both:

  • renderer.onError callback wired to device.onuncapturederror, mirroring renderer.onDeviceLost. Validation / OOM / internal errors raised outside an error scope now surface with their GPUError subclass (e.g. GPUValidationError) and message. Apps can override onError to handle them in their own UI.
  • Pipeline creation failures now call GPUShaderModule.getCompilationInfo() on the involved stages and log each message with line/column, source excerpt, and caret.
  • Compute pipeline creation is wrapped in a validation error scope (previously only render was).
  • createRenderPipelineAsync rejections are no longer swallowed, the reason is included in the logged failure.

Before:

THREE.WebGPURenderer: Render pipeline creation failed (...): [Invalid ShaderModule "fragment"] is invalid.

After:

THREE.WebGPURenderer: Render pipeline creation failed (renderPipeline_MeshBasicNodeMaterial_16): [Invalid ShaderModule "fragment"] is invalid.
THREE.WebGPURenderer [renderPipeline_... / fragment error] at line 33:4: identifiers must not start with two or more underscores                                                                                                                                           
  fn main( ) -> OutputStruct {
     ^                                                                                                                                                                                                                                                                     

Validation errors stay recoverable (the device keeps running); only real device loss still routes through onDeviceLost.

Example:
image
Should I include this example?

This contribution is funded by Spawn

RenaudRohlinger and others added 3 commits April 19, 2026 14:41
…nostics.

Today a crash in WebGPU usually ends as a generic "Device Lost" with an
unrecoverable-error message and no cause. This extends the error-reporting
surface to use more of the spec:

- Wire `device.onuncapturederror` and route it through a new
  `renderer.onError` callback (mirrors `renderer.onDeviceLost`), so
  validation / out-of-memory / internal errors raised outside an error
  scope are surfaced with their GPUError subclass name instead of being
  silently escalated to a device loss. Applications can override
  `renderer.onError` to handle these without crashing the device.

- On render and compute pipeline creation failures, call
  `GPUShaderModule.getCompilationInfo()` on the stages involved and log
  each message with line/column plus a source excerpt and caret, turning
  opaque validation failures into actionable WGSL feedback.

- Wrap compute pipeline creation in a validation error scope (previously
  only render pipelines were wrapped).

- Stop swallowing rejections from `createRenderPipelineAsync` — the
  rejection reason is now included in the logged failure.
The previous commit guarded each fire-and-forget call site of
`_reportShaderDiagnostics` with `.catch(() => {})`. That scattered
the "never propagate" contract across three call sites and left
user-injected custom loggers (via `setConsoleFunction`) and future
edits unprotected.

Wrap the helper body in a single top-level try/catch so the contract
lives with the code it protects; drop `.catch()` guards at the call
sites.
- `_onError`: drop the leading `THREE.` from the log message. `error()`
  already prepends it, so uncaptured errors were logged as
  `THREE.THREE.WebGPURenderer: Uncaptured ...`.

- Async render pipeline creation: wrap the Promise body in try/finally
  so `resolve()` is always called. If an unexpected throw escaped — for
  instance from a future await inserted above — the outer
  `Promise.all( promises )` in `compileAsync()` would hang forever,
  pinning the awaiting scene graph. Realistic paths are already safe
  today (`_reportShaderDiagnostics` has a top-level try/catch and
  `popErrorScope()` does not reject per spec), but making resolution
  unconditional costs nothing and removes the foot-gun.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 19, 2026

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 365.3
86.77
365.3
86.77
+0 B
+0 B
WebGPU 638.99
177.4
640.64
177.98
+1.65 kB
+580 B
WebGPU Nodes 637.11
177.11
638.76
177.69
+1.65 kB
+578 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 497.82
121.45
497.82
121.45
+0 B
+0 B
WebGPU 710.84
192.25
712.49
192.84
+1.66 kB
+593 B
WebGPU Nodes 660.06
179.57
661.71
180.16
+1.66 kB
+586 B

@RenaudRohlinger RenaudRohlinger added this to the r185 milestone Apr 19, 2026
RenaudRohlinger added a commit to RenaudRohlinger/three.js that referenced this pull request Apr 19, 2026
These changes belong to mrdoob#33418 and were bundled into this branch by mistake.

- Renderer.js: drop `onError` / `_onError` hook.
- WebGPUBackend.js: drop `device.onuncapturederror` → `renderer.onError` bridge.
- WebGPUPipelineUtils.js: revert to dev (removes pipeline-label error messages and `_reportShaderDiagnostics`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Makio64
Copy link
Copy Markdown
Contributor

Makio64 commented Apr 20, 2026

Thanks, this will improve a lot the devs & agent self-testing !

@mrdoob mrdoob requested a review from sunag April 21, 2026 09:17

for ( const { program, module } of stages ) {

if ( ! module || typeof module.getCompilationInfo !== 'function' ) continue;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes, for me, LLMs add many extra checks; could that be the case? Maybe the info checks bellow is also overloaded with verification?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants